﻿namespace Snipps.Controls
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;

    [TemplatePart(Name = PART_Watermark, Type = typeof(TextBlock))]
    public class TagsTextBox : RichTextBox
    {
        private const string PART_Watermark = "PART_Watermark";

        public static readonly RoutedEvent RemoveItemEvent = EventManager.RegisterRoutedEvent(
            "RemoveItem", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TagsTextBox));

        public static readonly DependencyProperty TagsProperty = DependencyProperty.Register(
            "Tags", 
            typeof(INotifyCollectionChanged), 
            typeof(TagsTextBox), 
            new PropertyMetadata(default(INotifyCollectionChanged), OnAssignTagsCollection));

        public static readonly DependencyProperty TokenTemplateProperty = DependencyProperty.Register(
            "TokenTemplate", typeof(DataTemplate), typeof(TagsTextBox));

        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
            "Text", typeof(string), typeof(TagsTextBox), new PropertyMetadata(default(string), OnTextPropertyChanged));

        public static readonly DependencyProperty WatermarkTextProperty = DependencyProperty.Register(
            "WatermarkText", typeof(string), typeof(TagsTextBox), new PropertyMetadata(default(string)));

        private IEnumerable<object> _enumerbaleTagsCollection;

        private bool isCreatingTagsContainers;

        public TagsTextBox()
        {
            Document = new FlowDocument(new Paragraph());

            TextChanged += OnTextEditorChanged;
            AcceptsReturn = false;

            GotFocus += (sender, args) => UpdateWatermarkVisibility();
            LostFocus += (sender, args) => UpdateWatermarkVisibility();
        }

        public string WatermarkText
        {
            get
            {
                return (string)GetValue(WatermarkTextProperty);
            }

            set
            {
                SetValue(WatermarkTextProperty, value);
            }
        }

        public string Text
        {
            get
            {
                return (string)GetValue(TextProperty);
            }

            set
            {
                SetValue(TextProperty, value);
            }
        }

        public string DisplayValueProperty { get; set; }

        public Type ObjectType { get; set; }

        public INotifyCollectionChanged Tags
        {
            get
            {
                return (INotifyCollectionChanged)GetValue(TagsProperty);
            }

            set
            {
                SetValue(TagsProperty, value);
            }
        }

        public DataTemplate TokenTemplate
        {
            get
            {
                return (DataTemplate)GetValue(TokenTemplateProperty);
            }

            set
            {
                SetValue(TokenTemplateProperty, value);
            }
        }

        public event RoutedEventHandler RemoveItem
        {
            add
            {
                AddHandler(RemoveItemEvent, value);
            }

            remove
            {
                RemoveHandler(RemoveItemEvent, value);
            }
        }

        private void RaiseRemoveItemEvent(object item)
        {
            var newEventArgs = new RemoveItemEventArgs(RemoveItemEvent) { RemovedItem = item };
            RaiseEvent(newEventArgs);
        }

        private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tagsTextBox = d as TagsTextBox;

            if (tagsTextBox == null)
            {
                return;
            }

            tagsTextBox.UpdateTextRun();
        }

        private void UpdateWatermarkVisibility()
        {
            if (Text == null || _enumerbaleTagsCollection == null)
            {
                return;
            }

            if (!string.IsNullOrEmpty(Text) || _enumerbaleTagsCollection.Any() || IsFocused)
            {
                {
                    var watermark = GetTemplateChild(PART_Watermark) as TextBlock;
                    if (watermark != null)
                    {
                        watermark.Visibility = Visibility.Collapsed;
                    }
                }
            }
            else
            {
                {
                    var watermark = GetTemplateChild(PART_Watermark) as TextBlock;
                    if (watermark != null)
                    {
                        watermark.Visibility = Visibility.Visible;
                    }
                }
            }
        }

        private void UpdateTextRun()
        {
            UpdateWatermarkVisibility();

            if (!isCreatingTagsContainers)
            {
                var para = CaretPosition.Paragraph;
                if (para != null)
                {
                    var run = para.Inlines.FirstOrDefault(x => x is Run) as Run;
                    if (run != null)
                    {
                        if (!string.IsNullOrEmpty(Text))
                        {
                            run.Text = Text;
                            CaretPosition = CaretPosition.DocumentEnd;
                        }
                        else
                        {
                            para.Inlines.Remove(run);
                        }
                    }
                }
            }
        }

        private static void OnAssignTagsCollection(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var tagsTextBox = d as TagsTextBox;

            if (tagsTextBox == null)
            {
                return;
            }

            var old = e.OldValue as INotifyCollectionChanged;

            if (old != null)
            {
                old.CollectionChanged -= (sender, args) => tagsTextBox.CreateTagsContainers();
            }

            var n = e.NewValue as INotifyCollectionChanged;

            if (n != null)
            {
                n.CollectionChanged += (sender, args) => tagsTextBox.CreateTagsContainers();
                tagsTextBox._enumerbaleTagsCollection = e.NewValue as IEnumerable<object>;
                tagsTextBox.CreateTagsContainers();
            }
        }

        private void CreateTagsContainers()
        {
            if (_enumerbaleTagsCollection == null)
            {
                return;
            }

            UpdateWatermarkVisibility();
            TextChanged -= OnTextEditorChanged;
            isCreatingTagsContainers = true;
            var para = CaretPosition.Paragraph;

            if (para == null)
            {
                para = Document.Blocks.LastBlock as Paragraph;
            }

            para.Inlines.Clear();

            foreach (var tag in _enumerbaleTagsCollection)
            {
                var tokenContainer = CreateTokenContainer(tag);
                para.Inlines.Add(tokenContainer);
            }

            CaretPosition = CaretPosition.DocumentEnd;
            isCreatingTagsContainers = false;
            TextChanged += OnTextEditorChanged;
        }

        private InlineUIContainer CreateTokenContainer(object token)
        {
            var presenter = new ContentPresenter
                                {
                                    Content = token, 
                                    ContentTemplate = TokenTemplate, 
                                    Height = Math.Ceiling(FontSize * FontFamily.LineSpacing), 
                                };

            // BaselineAlignment is needed to align with Run
            return new InlineUIContainer(presenter) { BaselineAlignment = BaselineAlignment.TextBottom };
        }

        private void RemoveItemContainer(InlineUIContainer tagedElementContainer)
        {
            var contentPresenter = tagedElementContainer.Child as ContentPresenter;
            if (contentPresenter != null)
            {
                var displayValue = contentPresenter.Content;
                RaiseRemoveItemEvent(displayValue);
            }
        }

        private void OnTextEditorChanged(object sender, TextChangedEventArgs e)
        {
            TextChanged -= OnTextEditorChanged;
            var text = CaretPosition.GetTextInRun(LogicalDirection.Backward);
            Text = text;
            TextChanged += OnTextEditorChanged;
        }

        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            InlineUIContainer elementContainerToRemove = null;
            if (e.Key == Key.Back || e.Key == Key.Delete)
            {
                if (e.Key == Key.Back)
                {
                    elementContainerToRemove = CaretPosition.GetAdjacentElement(LogicalDirection.Backward) as InlineUIContainer;
                }
                else if (e.Key == Key.Delete)
                {
                    elementContainerToRemove = CaretPosition.GetAdjacentElement(LogicalDirection.Forward) as InlineUIContainer;
                }

                if (elementContainerToRemove != null)
                {
                    RemoveItemContainer(elementContainerToRemove);
                    e.Handled = true;
                }
            }
            else if (e.Key == Key.E && e.KeyboardDevice.Modifiers == ModifierKeys.Control)
            {
                e.Handled = true;
            }

            base.OnPreviewKeyDown(e);
        }
    }
}